package twoqueues

import (
	algo "SpringCache/cachealgorithm"
	"container/list"
	"math/rand"
	"time"
)

var DefaultMaxBytes int64 = 10
var DefaultExpireRandom time.Duration = 3 * time.Minute

type NowFunc func() time.Time

var nowFunc NowFunc = time.Now

type Cache struct {
	maxBytes     int64                         // 允许使用的最大内存
	nbytes       int64                         // 当前已经使用的内存
	fifoQueue    *list.List                    // FIFO队列
	lruQueue     *list.List                    // LRU队列
	cache        map[string]*list.Element      // 实际保存键值的缓存
	OnEvicted    func(key string, value Value) // 当节点被删除时可以选择性调用回调函数
	Now          NowFunc
	ExpireRandom time.Duration
}

type entry struct {
	key     string
	value   Value
	expire  time.Time // 过期时间
	addTime time.Time
	queue   int // 标记元素所在队列，0表示FIFO队列，1表示LRU队列
}

type Value = algo.Value

func New(maxBytes int64, onEvicted func(string, Value)) *Cache {
	if maxBytes > DefaultMaxBytes {
		return &Cache{
			maxBytes:     DefaultMaxBytes,
			fifoQueue:    list.New(),
			lruQueue:     list.New(),
			cache:        make(map[string]*list.Element),
			OnEvicted:    onEvicted,
			Now:          nowFunc,
			ExpireRandom: DefaultExpireRandom,
		}
	}
	return &Cache{
		maxBytes:     maxBytes,
		fifoQueue:    list.New(),
		lruQueue:     list.New(),
		cache:        make(map[string]*list.Element),
		OnEvicted:    onEvicted,
		Now:          nowFunc,
		ExpireRandom: DefaultExpireRandom,
	}
}

func (c *Cache) Len() int {
	return c.fifoQueue.Len() + c.lruQueue.Len()
}

// Get 是用于处理Two-Queues逻辑的函数
func (c *Cache) Get(key string) (value Value, ok bool) {
	if ele, ok := c.cache[key]; ok {
		kv := ele.Value.(*entry)
		// 如果kv过期了，将它们移除缓存
		if kv.expire.Before(c.Now()) {
			c.removeElement(ele)
			return nil, false
		}
		// 如果元素在FIFO队列中，将其移动到LRU队列
		if kv.queue == 0 {
			c.fifoQueue.Remove(ele)
			newEle := c.lruQueue.PushFront(kv)
			c.cache[key] = newEle
			kv.queue = 1
		} else {
			// 如果元素在LRU队列中，将其移动到队首
			c.lruQueue.MoveToFront(ele)
		}
		// 更新键值对的添加时间和过期时间
		expireTime := kv.expire.Sub(kv.addTime)
		kv.expire = c.Now().Add(expireTime)
		kv.addTime = c.Now()
		return kv.value, true
	}
	return nil, false
}

func (c *Cache) RemoveOldest() {
	// 优先从FIFO队列中移除元素
	if c.fifoQueue.Len() > 0 {
		ele := c.fifoQueue.Back()
		c.fifoQueue.Remove(ele)
		kv := ele.Value.(*entry)
		delete(c.cache, kv.key)
		c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
		if c.OnEvicted != nil {
			c.OnEvicted(kv.key, kv.value)
		}
		return
	}
	// 如果FIFO队列为空，从LRU队列中移除元素
	if c.lruQueue.Len() > 0 {
		ele := c.lruQueue.Back()
		c.lruQueue.Remove(ele)
		kv := ele.Value.(*entry)
		delete(c.cache, kv.key)
		c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
		if c.OnEvicted != nil {
			c.OnEvicted(kv.key, kv.value)
		}
	}
}

func (c *Cache) Remove(key string) {
	if ele, ok := c.cache[key]; ok {
		c.removeElement(ele)
	}
}

func (c *Cache) removeElement(ele *list.Element) {
	kv := ele.Value.(*entry)
	if kv.queue == 0 {
		c.fifoQueue.Remove(ele)
	} else {
		c.lruQueue.Remove(ele)
	}
	delete(c.cache, kv.key)
	c.nbytes -= int64(len(kv.key)) + int64(kv.value.Len())
	if c.OnEvicted != nil {
		c.OnEvicted(kv.key, kv.value)
	}
}

func (c *Cache) Add(key string, value Value, expire time.Time) {
	// randDuration 是用户添加的过期时间进行一定范围的随机，用于防止大量缓存同一时间过期而发生缓存雪崩
	randDuration := time.Duration(rand.Int63n(int64(c.ExpireRandom)))

	if ele, ok := c.cache[key]; ok {
		// 如果key已经存在则将value替换
		if ele.Value.(*entry).queue == 0 {
			c.fifoQueue.MoveToFront(ele)
		} else {
			c.lruQueue.MoveToFront(ele)
		}
		kv := ele.Value.(*entry)
		c.nbytes += int64(value.Len()) - int64(kv.value.Len())
		kv.value = value
		kv.expire = expire.Add(randDuration)
	} else {
		// 新元素添加到FIFO队列
		newEle := c.fifoQueue.PushFront(&entry{key, value, expire.Add(randDuration), c.Now(), 0})
		c.cache[key] = newEle
		c.nbytes += int64(len(key)) + int64(value.Len())
	}
	for c.maxBytes != 0 && c.maxBytes < c.nbytes {
		c.RemoveOldest()
	}
}
